﻿// Shader-Based-Image-Processing (SBIP)
// http://code.google.com/p/sbip/
//
// Thanks to Roque Patrick. Most of source code of this class is written by him 
// (see also http://roque-patrick.com/windows/final/bbl0093.html).
//
// Copyright © Frank Nagl, 2009-2011
// admin@franknagl.de
//
namespace SBIP.Helper.Generics
{
    using System;
    using System.Collections;
    using System.Collections.Generic;

    /// <summary>
    /// Extends dotnet's <see cref="Dictionary{TKey,TValue}"/> for using 
    /// multiple keys. The MultiDictionary<list type="TKey, TValue"/> can be 
    /// created like a normal dictionary, but it’s possible to use the 
    /// same key multiple times.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    /// <remarks>
    /// <para>
    /// Most of the source code is written by Roque Patrick (see also 
    /// <a href="http://roque-patrick.com/windows/final/bbl0093.html"/>).
    /// </para>
    /// 
    /// <para>
    /// The MultiDictionary<list type="TKey, TValue"/> can be created like a 
    /// normal dictionary, but here it’s possible to use the same key multiple 
    /// times. In this case, the country is used as the key and Racer as value. 
    /// Two racers from Australia and two racers from the United Kingdom are 
    /// added to the dictionary. The indexer of this dictionary returns a list 
    /// that is iterated through with a foreach loop.
    /// </para>
    /// <para><b>Sample usage:</b></para>
    /// <code>
    /// MultiDictionary<list type="string, Racer"/> racers = 
    ///                     new MultiDictionary<list type="string, Racer"/>();
    /// 
    /// racers.Add("Canada", new Racer("Jacqes", "Villeneuve", "Canada", 11));
    /// racers.Add("Australia", new Racer("Alan", "Jones", "Australia", 12));
    /// racers.Add("United Kingdom", new Racer("Jackie", "Stewart",
    ///       "United Kingdom", 27));
    /// racers.Add("United Kingdom", new Racer("James", "Hunt",
    ///       "United Kingdom", 10));
    /// racers.Add("Australia", new Racer("Jack", "Brabham", "Australia", 14));
    /// 
    /// foreach (Racer r in racers["Australia"])
    /// {
    ///    Console.WriteLine(r);
    /// }
    /// </code>
    /// 
    /// <para>The output shows the racers from Australia:</para>
    /// <code>
    /// Alan Jones
    /// Jack Brabham
    /// </code>
    /// 
    /// </remarks>
    public class MultiDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        #region enums, statics and constants
        #endregion enums, statics and constants

        #region members
        private readonly Dictionary<TKey, List<TValue>> dict =
                                     new Dictionary<TKey, List<TValue>>();
        #endregion members

        #region properties

        /// <summary>
        /// Gets the element with the specified key.
        /// </summary>
        /// <returns>The element with the specified key.</returns>
        ///   
        /// <exception cref="T:System.ArgumentNullException"><paramref name="key"/> 
        /// is null.</exception>
        ///   
        /// <exception cref="T:System.Collections.Generic.KeyNotFoundException">
        /// The property is retrieved and <paramref name="key"/> is not found.
        /// </exception>
        ///   
        /// <exception cref="T:System.NotSupportedException">The property is 
        /// set and the <see cref="T:System.Collections.Generic.IDictionary`2"/> 
        /// is read-only.</exception>
        public IList<TValue> this[TKey key]
        {
            get
            {
                return dict[key];
            }
        }

        public int Count
        {
            get
            {
                int count = 0;

                Dictionary<TKey, List<TValue>>.Enumerator enumerator = 
                    dict.GetEnumerator();

                while (enumerator.MoveNext())
                {

                    KeyValuePair<TKey, List<TValue>> pair = enumerator.Current;
                    count += pair.Value.Count;
                }
                return count;
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                return dict.Keys;
            }
        }

        public bool Remove(TKey key)
        {
            return dict.Remove(key);
        }

        public ICollection<TValue> Values
        {
            get
            {
                List<TValue> values = new List<TValue>();

                Dictionary<TKey, List<TValue>>.Enumerator enumerator = dict.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    values.AddRange(enumerator.Current.Value);
                }
                return values;
            }
        }

        #endregion properties

        #region public methods

        public void Add(TKey key, TValue value)
        {
            List<TValue> list;
            if (dict.TryGetValue(key, out list))
            {
                list.Add(value);
            }
            else
            {
                list = new List<TValue>();
                list.Add(value);
                dict.Add(key, list);
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        public void Clear()
        {
            dict.Clear();
        }

        public bool ContainsKey(TKey key)
        {
            return dict.ContainsKey(key);
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            List<TValue> list;
            if (!dict.TryGetValue(item.Key, out list))
            {
                return false;
            }
            return list.Contains(item.Value);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            if (array == null)
            {
                throw new ArgumentNullException("array");
            }

            if (arrayIndex < 0 || arrayIndex > array.Length)
            {
                throw new ArgumentOutOfRangeException(
                                    "arrayIndex", @"Array index out of range.");
            }

            if (array.Length - arrayIndex < Count)
            {
                throw new ArgumentException("Array too small");
            }

            Dictionary<TKey, List<TValue>>.Enumerator enumerator = 
                                                        dict.GetEnumerator();
            while (enumerator.MoveNext())
            {
                KeyValuePair<TKey, List<TValue>> mapPair = enumerator.Current;
                foreach (TValue val in mapPair.Value)
                {
                    array[arrayIndex++] = 
                        new KeyValuePair<TKey, TValue>(mapPair.Key, val);
                }
            }
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            Dictionary<TKey, List<TValue>>.Enumerator enumerateKeys =
                dict.GetEnumerator();

            while (enumerateKeys.MoveNext())
            {
                foreach (TValue val in enumerateKeys.Current.Value)
                {
                    KeyValuePair<TKey, TValue> pair = new KeyValuePair<TKey, TValue>(
                        enumerateKeys.Current.Key, val);
                    yield return pair;
                }
            }
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            List<TValue> list;
            return dict.TryGetValue(item.Key, out list) && list.Remove(item.Value);
        }        

        #endregion public methods
        
        #region explicit interface implementaions        

        bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value)
        {
            throw new NotSupportedException("TryGetValue is not supported.");
        }

        TValue IDictionary<TKey, TValue>.this[TKey key]
        {
            get
            {
                throw new NotSupportedException(
                      "Accessing elements by key is not supported.");
            }
            set
            {
                throw new NotSupportedException(
                      "Accessing elements by key is not supported.");
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion explicit interface implementaions
    }
}
